package at.ac.tuwien.dse.health.geo;

import at.ac.tuwien.dse.health.entity.GeoHospital;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Doubles;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.*;

import javax.inject.Inject;
import java.util.Collections;
import java.util.Comparator;

import static com.google.common.collect.Iterables.isEmpty;

/**
 * Uses a {@link GeoHospitalRepository} for locating {@link GeoHospital GeoHospitals} within a certain range.
 *
 * @author Bernhard Nickel
 * @author Gregor Schauer
 * @author Dominik Strasser
 * @see GeoHospitalRepository
 */
public class GeoHospitalService {
	static final int MAX_RANGE = 10000;
	@Inject
	GeoHospitalRepository repository;
	@Inject
	MongoTemplate mongoTemplate;
	int zoneRange = 20;

	/**
	 * Returns geographic information of the {@link GeoHospital} closest to the given coordinate.
	 *
	 * @param point the coordinate
	 * @return the hospital found
	 * @see #findInRange(org.springframework.data.mongodb.core.geo.Circle)
	 */
	public GeoResult<GeoHospital> findClosestHospital(Point point) {
		GeoResults<GeoHospital> hospitals = new GeoResults<GeoHospital>(Collections.<GeoResult<GeoHospital>>emptyList());
		// Incrementally increase the search range until the maximum is reached or at least one GeoHospital is found.
		for (int r = zoneRange; hospitals.getContent().isEmpty() && r < MAX_RANGE; r += zoneRange) {
			hospitals = findInRange(new Circle(point, r));
		}
		return getClosestHospital(hospitals);
	}

	/**
	 * Returns the {@link GeoHospital} with the minimum normalized distance.
	 *
	 * @param hospitals the hospitals
	 * @return the hospital with the minimum distance
	 */
	protected GeoResult<GeoHospital> getClosestHospital(Iterable<GeoResult<GeoHospital>> hospitals) {
		return isEmpty(hospitals) ? null : Ordering.from(new Comparator<GeoResult>() {
			@Override
			public int compare(GeoResult o1, GeoResult o2) {
				return Doubles.compare(o1.getDistance().getNormalizedValue(), o2.getDistance().getNormalizedValue());
			}
		}).min(hospitals.iterator());
	}

	/**
	 * Returns geographic information of all {@link GeoHospital GeoHospitals} within the area defined by the given
	 * {@link Circle}.
	 *
	 * @param circle the area
	 * @return the hospitals found
	 * @see GeoHospitalRepository#findByLocationNear(org.springframework.data.mongodb.core.geo.Point, org.springframework.data.mongodb.core.geo.Distance)
	 */
	protected GeoResults<GeoHospital> findInRange(Circle circle) {
		return repository.findByLocationNear(circle.getCenter(), new Distance(circle.getRadius(), Metrics.KILOMETERS));
	}

	public void setZoneRange(int zoneRange) {
		this.zoneRange = zoneRange;
	}
}
